package com.na.game.opengl;

import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.NinePatch;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.FloatMath;
import android.util.SparseArray;

import com.na.game.graphics.Graphics;
import com.na.game.graphics.Image;
import com.na.game.opengl.GLPaint.AtomicPaint;
import com.na.game.util.GameConfig;
import com.na.game.util.Util;
import com.na.game.widget.Layout;

public class GLGraphics extends Graphics {

	protected int color = 0xFFFFFFFF;
	protected int filterColor = 0xFFFFFFFF;
	protected int renderColor = 0xFFFFFFFF; // 绘制颜色
	protected Rect clip;
	protected float scale = 1f;
	protected float tx, ty; // 坐标偏移
	
	protected List<GLPaint> paints;
	protected GLPaint currentPaint;
	
	private static short[] vertices = new short[24]; // vertex buffer
	protected static int[] texturePointsBuffer = new int[12]; // texture point buffer
	private static float[] matrixBuffer = new float[8];
	
	public GLGraphics(Typeface font) {
		paints = new ArrayList<GLPaint>();
		currentPaint = new GLPaint();
		paint = new Paint();
		paint.setTextSize(GameConfig.FONT_DEFAULT_SIZE);
		paint.setColor(0xFFFFFFFF);
		if (font != null) {
			paint.setTypeface(font);
		}
		fmiCache = new SparseArray<FontMetricsInt>();
		fmiCache.put(GameConfig.FONT_DEFAULT_SIZE, paint.getFontMetricsInt());
		colorFilterCache = new SparseArray<ColorMatrixColorFilter>();
	}
	
	public void setScale(float scale) {
		this.scale = scale;
	}
	
	public void translate(float tx, float ty) {
		this.tx = tx;
		this.ty = ty;
	}
	
	@Override
	public void setCanvas(Canvas canvas) {
		throw new UnsupportedOperationException("setCanvas(Canvas canvas)");
	}

	@Override
	public void setColor(int argb) {
		if ((argb >> 24 & 0xFF) == 0) {
			argb |= 0xFF000000;
		}
		color = argb;
		renderColor = filterColor(argb, filterColor);
//		paint.setColor(renderColor);
	}

	@Override
	public void setColor(int alpha, int red, int green, int blue) {
		if (alpha < 0 || alpha > 0xFF
				|| red < 0 || red > 0xFF
				|| green < 0 || green > 0xFF
				|| blue < 0 || blue > 0xFF) {
			throw new IllegalArgumentException("color out of range");
		}
		setColor((alpha << 24) | (red << 16) | (green << 8) | blue);
	}
	
	@Override
	public int getClipX() {
		return clip != null ? clip.left : 0;
	}

	@Override
	public int getClipY() {
		return clip != null ? clip.top : 0;
	}

	@Override
	public int getClipWidth() {
		return clip != null ? clip.width(): (int) (GameConfig.screenWidth / scale);
	}

	@Override
	public int getClipHeight() {
		return clip != null ? clip.height(): (int) (GameConfig.screenHeight / scale);
	}

	@Override
	public void clipRect(int x, int y, int width, int height) {
		if (clip == null) {
			setClip(x, y, width, height);
		} else {
			Rect newClip = new Rect(x, y, x + width, y + height);
			if (newClip.intersect(clip)) {
				setClip(newClip.left, newClip.top, newClip.width(), newClip.height());
			} else {
				setClip(0, 0, 0, 0); // set empty
			}
		}
	}

	@Override
	public void setClip(int x, int y, int width, int height) {
		clip = new Rect(x, y, x + width, y + height);
		GLPaint paint = currentPaint;
		if (paint.isEmpty()) {
			paint.setClip(clip);
		}
	}

	@Override
	public Rect getClip() {
		return clip;
	}
	
	protected boolean checkApplyClip(int x, int y, int width, int height) {
		Rect currentPaintClip = currentPaint.getClip();
		if (currentPaintClip != null && !currentPaintClip.contains(x, y, x + width, y + height)) { // 不在当前绘制范围内(检查currentPaint)
			newBatch();
			currentPaintClip = clip;
		} else if (clip != null && !clip.equals(currentPaintClip) && !clip.contains(x, y, x + width, y + height)) { // 检查clip
			newBatch();
			currentPaintClip = clip;
		}
		return currentPaintClip == null || Util.isIntersect(currentPaintClip, x, y, x + width, y + height);
	}
	
	private void newBatch() {
		GLPaint paint = currentPaint;
		if (!paint.isEmpty()) {
			paint.setScale(scale);
			paint.setTranslate(tx, ty);
			paints.add(paint);
		}
		paint = new GLPaint();
		paint.setClip(clip);
		currentPaint = paint;
	}

	@Override
	public void drawLine(int x1, int y1, int x2, int y2) {
		if (!checkApplyClip(x1, y1, x2 - x1, y2 - y1))
			return;
		y1 = -y1;
		y2 = -y2;
		
		int i = 0;
		vertices[i++] = (short) x1;
		vertices[i++] = (short) y1;
		vertices[i++] = (short) x2;
		vertices[i++] = (short) y2;
		GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		AtomicPaint ap = new AtomicPaint(renderColor, i >> 1, vertex, true);
		
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void fillRect(int x, int y, int width, int height) {
		if (!checkApplyClip(x, y, width, height))
			return;
		y = -y;
		height = -height;
		
		int i = 0;
		vertices[i++] = (short) x;
		vertices[i++] = (short) y;
		vertices[i++] = (short) x;
		vertices[i++] = (short) (y + height);
		vertices[i++] = (short) (x + width);
		vertices[i++] = (short) (y + height);
		vertices[i++] = (short) (x + width);
		vertices[i++] = (short) (y + height);
		vertices[i++] = (short) (x + width);
		vertices[i++] = (short) y;
		vertices[i++] = (short) x;
		vertices[i++] = (short) y;
		
		GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		AtomicPaint ap = new AtomicPaint(renderColor, i >> 1, vertex, false);
		
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void drawRect(int x, int y, int width, int height) {
		if (!checkApplyClip(x, y, width, height))
			return;
		
		y = -y;
		height = -height;
		
		int i = 0;
		vertices[i++] = (short) x;
		vertices[i++] = (short) y;
		vertices[i++] = (short) x;
		vertices[i++] = (short) (y + height);
		vertices[i++] = (short) (x + width);
		vertices[i++] = (short) (y + height);
		vertices[i++] = (short) (x + width);
		vertices[i++] = (short) y;
		vertices[i++] = (short) x;
		vertices[i++] = (short) y;
		
		GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		AtomicPaint ap = new AtomicPaint(renderColor, i >> 1, vertex, true);
		
		currentPaint.addAtomicPaint(ap);
	}
	
	private int[] roundPosBuffer;
	private int arcWidthBuffer, arcHeightBuffer;
	private int[] generateRoundPos(int arcWidth, int arcHeight) {
		// 椭圆标准方程:(a>b>0 a:半长轴  b:半短轴)
		// 当焦点在x轴时:x^2/a^2+y^2/b^2=1
		// 当焦点在y轴时:y^2/a^2+x^2/b^2=1
		if (roundPosBuffer != null && arcWidthBuffer == arcWidth && arcHeightBuffer == arcHeight) {
			return roundPosBuffer;
		}
		
		int a, b;
		int[] pos;
		if (arcWidth > arcHeight) { // 焦点x轴
			a = arcWidth;
			b = arcHeight;
			float aa = a * a;
			float bb = b * b;
			pos = new int[(b + 1) * 2];
			for (int y = 0; y <= b; y++) {
				pos[y * 2] = (int) FloatMath.sqrt(aa - aa * y * y / bb);
				pos[y * 2 + 1] = arcHeight - y;
			}
		} else {
			a = arcHeight;
			b = arcWidth;
			float aa = a * a;
			float bb = b * b;
			pos = new int[(b + 1) * 2];
			for (int x = 0; x <= b; x++) {
				pos[x * 2] = x;
				pos[x * 2 + 1] = (int) (arcHeight - FloatMath.sqrt(aa - aa * x * x / bb));
			}
		}
		
//		System.out.println("------------------");
//		for (int i = 0; i < pos.length; i++) {
//			System.out.print(pos[i] + ",");
//		}
//		System.out.println("------------------");
		
		roundPosBuffer = pos;
		arcWidthBuffer = arcWidth;
		arcHeightBuffer = arcHeight;
		
		return pos;
	}

	@Override
	public void drawRoundRect(int x, int y, int width, int height,
			int arcWidth, int arcHeight) {
		if (!checkApplyClip(x, y, width, height)) {
			return;
		}
		
		// 上
		drawLine(x + arcWidth - 1, y, x + width - arcWidth, y);
		// 下
		drawLine(x + arcWidth - 1, y + height - 1, x + width - arcWidth, y + height - 1);
		// 左
		drawLine(x, y + arcHeight - 1, x, y + height - arcHeight);
		// 右
		drawLine(x + width - 1, y + arcHeight - 1, x + width - 1, y + height - arcHeight);
		
		int[] pos = generateRoundPos(arcWidth, arcHeight);
		int len = pos.length;
		for (int i = 2; i < len; i += 2) {
			int x1 = pos[i - 2];
			int y1 = pos[i - 1];
			int x2 = pos[i];
			int y2 = pos[i + 1];
			// 左上
			drawLine(x + arcWidth - x1, y + y1, x + arcWidth - x2, y + y2);
			// 左下
			drawLine(x + arcWidth - x1, y + height - y1, x + arcWidth - x2, y + height - y2);
			// 右上
			drawLine(x + width - arcWidth + x1, y + y1, x + width - arcWidth + x2, y + y2);
			// 右下
			drawLine(x + width - arcWidth + x1, y + height- y1, x + width - arcWidth + x2, y + height- y2);
		}
	}

	@Override
	public void fillRoundRect(int x, int y, int width, int height,
			int arcWidth, int arcHeight) {
		if (!checkApplyClip(x, y, width, height)) {
			return;
		}
		
		// 左
		fillRect(x, y + arcHeight, arcWidth, height - 2 * arcHeight);
		// 右
		fillRect(x + width - arcWidth, y + arcHeight, arcWidth, height - 2 * arcHeight);
		// 中
		fillRect(x + arcWidth, y, width - 2 * arcWidth, height);
		
		int[] pos = generateRoundPos(arcWidth, arcHeight);
		int len = pos.length;
		for (int i = 2; i < len; i += 2) {
			int x1 = pos[i - 2];
			int y1 = pos[i - 1];
			int x2 = pos[i];
			int y2 = pos[i + 1];
			
			int x3 = x + arcWidth; // 左上顶点
			int y3 = y + arcHeight;
			// 左上
			fillTriangle(x + arcWidth - x1, y + y1, x + arcWidth - x2, y + y2, x3, y3);
			// 左下
			x3 = x + arcWidth; // 左下顶点
			y3 = y + height - arcHeight;
			fillTriangle(x + arcWidth - x1, y + height - y1, x + arcWidth - x2, y + height - y2, x3, y3);
			// 右上
			x3 = x + width - arcWidth; // 右上顶点
			y3 = y + arcHeight;
			fillTriangle(x + width - arcWidth + x1, y + y1, x + width - arcWidth + x2, y + y2, x3, y3);
			// 右下
			x3 = x + width - arcWidth; // 右下顶点
			y3 = y + height - arcHeight;
			fillTriangle(x + width - arcWidth + x1, y + height- y1, x + width - arcWidth + x2, y + height- y2, x3, y3);
		}
	}

	@Override
	public void fillArc(int x, int y, int width, int height, int startAngle,
			int arcAngle) {
		// TODO
		throw new UnsupportedOperationException("GLGraphics.fillArc");
	}

	@Override
	public void drawArc(int x, int y, int width, int height, int startAngle,
			int arcAngle) {
		// TODO
		throw new UnsupportedOperationException("GLGraphics.drawArc");
	}

	@Override
	public void drawString(String str, int x, int y, int anchor) {
		FontMetricsInt fmi = fmiCache.get(getTextSize());
		if (fmi == null) {
			fmi = paint.getFontMetricsInt();
			fmiCache.put(getTextSize(), fmi);
		}
		int width = Util.getTextWidth(str, getTextSize());
		int height = Util.getTextHeight(str, getTextSize()); // fmi.bottom - fmi.top; // test
    	int horizontal = anchor & (HCENTER | LEFT | RIGHT);
    	switch (horizontal) {
    		case HCENTER:
    			x -= width >> 1;
    			break;
    		case RIGHT:
    			x -= width;
    			break;
    	}
    	int vertical = anchor & (VCENTER | TOP | BOTTOM | BASELINE);
    	switch (vertical) {
    		case VCENTER:
    			y -= height >> 1;
    			break;
    		case BOTTOM:
    			y -= height;
    			break;
    		case BASELINE:
    			y += fmi.top;
    			break;
    	}
    	if (!checkApplyClip(x, y, width, height)) {
    		return;
    	}
    	
    	
    	y = -y;
		height = -height;
    	
    	int i = 0;
    	vertices[i++] = (short) x;
    	vertices[i++] = (short) y;
    	vertices[i++] = (short) x;
    	vertices[i++] = (short) (y + height);
    	vertices[i++] = (short) (x + width);
    	vertices[i++] = (short) (y + height);
    	vertices[i++] = (short) (x + width);
    	vertices[i++] = (short) (y + height);
    	vertices[i++] = (short) (x + width);
    	vertices[i++] = (short) y;
    	vertices[i++] = (short) x;
    	vertices[i++] = (short) y;
    	
    	GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		
		GLTexture texture = GLTextureManager.registerFont(str, paint, -fmi.top);
		AtomicPaint ap = new AtomicPaint(renderColor, i >> 1, vertex, texture, texture.getTexturePointer(GLTextureManager.getFontImage(str, paint)));
    	
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void drawImage(Image image, int x, int y, int anchor) {
		int width = image.getWidth();
		int height = image.getHeight();
		x = transX(image.getWidth(), x, anchor);
		y = transY(image.getHeight(), y, anchor);
		if (!checkApplyClip(x, y, width, height))
			return;
		
		y = -y;
		height = -height;
    	
    	int i = 0;
    	vertices[i++] = (short) x;
    	vertices[i++] = (short) y;
    	vertices[i++] = (short) x;
    	vertices[i++] = (short) (y + height);
    	vertices[i++] = (short) (x + width);
    	vertices[i++] = (short) (y + height);
    	vertices[i++] = (short) (x + width);
    	vertices[i++] = (short) (y + height);
    	vertices[i++] = (short) (x + width);
    	vertices[i++] = (short) y;
    	vertices[i++] = (short) x;
    	vertices[i++] = (short) y;
    	
    	GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		
		GLTexture texture = GLTextureManager.registerImage((GLImage) image);
		
		AtomicPaint ap = new AtomicPaint(filterColor, i >> 1, vertex, texture, texture.getTexturePointer((GLImage) image));
    	
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void drawNinePatch(NinePatch np, Rect bound) {
		Bitmap bitmap = Bitmap.createBitmap(bound.width(), bound.height(), Config.ARGB_8888);
		Canvas canvas = new Canvas(bitmap);
		np.draw(canvas, new Rect(0, 0, bound.width(), bound.height()));
		
		GLImage image = new GLImage(bitmap, "NinePatch");
		drawImage(image, bound.left, bound.top, Layout.TOP_LEFT);
	}

	@Override
	public void drawImage(Image image, Rect src, Rect dst) {
		if (!checkApplyClip(dst.left, dst.top, dst.width(), dst.height())) {
			return;
		}
		
		int left = dst.left;
		int right = dst.right;
		int top = -dst.top;
		int bottom = -dst.bottom;
		int i = 0;
		vertices[i++] = (short) left;
		vertices[i++] = (short) top;
		vertices[i++] = (short) left;
		vertices[i++] = (short) bottom;
		vertices[i++] = (short) right;
		vertices[i++] = (short) bottom;
		vertices[i++] = (short) right;
		vertices[i++] = (short) bottom;
		vertices[i++] = (short) right;
		vertices[i++] = (short) top;
		vertices[i++] = (short) left;
		vertices[i++] = (short) top;
		
		GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		
		int preferredWidth = ((GLImage) image).getPreferredWidth();
		int preferredHeight = ((GLImage) image).getPreferredHeight();
		GLTexture texture = GLTextureManager.registerImage((GLImage) image, preferredWidth, preferredHeight);
		
		float width = texture.width;
		float height = texture.height;
		int[] pos = texture.getImagePos((GLImage) image);
		left = Float.floatToRawIntBits((src.left + pos[0]) / width);
		top = Float.floatToRawIntBits((src.top + pos[1]) / height);
		right = Float.floatToRawIntBits((src.right + pos[0]) / width);
		bottom = Float.floatToRawIntBits((src.bottom + pos[1]) / height);
		
		int j = 0;
		texturePointsBuffer[j++] = left;
		texturePointsBuffer[j++] = top;
		texturePointsBuffer[j++] = left;
		texturePointsBuffer[j++] = bottom;
		texturePointsBuffer[j++] = right;
		texturePointsBuffer[j++] = bottom;
		texturePointsBuffer[j++] = right;
		texturePointsBuffer[j++] = bottom;
		texturePointsBuffer[j++] = right;
		texturePointsBuffer[j++] = top;
		texturePointsBuffer[j++] = left;
		texturePointsBuffer[j++] = top;
		
		GLFloatBuffer texturePointer = GLBufferManager.allocateFloatBuffer(12);
		texturePointer.put(texturePointsBuffer, 0, j);
		
		AtomicPaint ap = new AtomicPaint(filterColor, i >> 1, vertex, texture, texturePointer);
    	
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void drawImage(Image image, Matrix matrix) {
		int left = 0;
		int top = 0;
		int right = image.getWidth();
		int bottom = image.getHeight();
		matrixBuffer[0] = left; // 左上
		matrixBuffer[1] = top;
		matrixBuffer[2] = left; // 左下
		matrixBuffer[3] = bottom;
		matrixBuffer[4] = right; // 右下
		matrixBuffer[5] = bottom;
		matrixBuffer[6] = right; // 右上
		matrixBuffer[7] = top;
		matrix.mapPoints(matrixBuffer);
		
		short ltx = (short) matrixBuffer[0];
		short lty = (short) -matrixBuffer[1];
		short lbx = (short) matrixBuffer[2];
		short lby = (short) -matrixBuffer[3];
		short rbx = (short) matrixBuffer[4];
		short rby = (short) -matrixBuffer[5];
		short rtx = (short) matrixBuffer[6];
		short rty = (short) -matrixBuffer[7];
		
    	int i = 0;
    	vertices[i++] = ltx; // 左上
    	vertices[i++] = lty;
    	vertices[i++] = lbx; // 左下
    	vertices[i++] = lby;
    	vertices[i++] = rbx; // 右下
    	vertices[i++] = rby;
    	vertices[i++] = rbx; // 右下
    	vertices[i++] = rby;
    	vertices[i++] = rtx; // 右上
    	vertices[i++] = rty;
    	vertices[i++] = ltx; // 左上
    	vertices[i++] = lty;
    	
    	GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		
		GLTexture texture = GLTextureManager.registerImage((GLImage) image);
		AtomicPaint ap = new AtomicPaint(filterColor, i >> 1, vertex, texture, texture.getTexturePointer((GLImage) image));
    	
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void drawRegion(Image image, int x_src, int y_src, int width,
			int height, int transform, int x_dest, int y_dest, int anchor) {
		Bitmap dest = Bitmap.createBitmap(image.getBitmap()/*TODO: bug:will be null after image binded*/, x_src, y_src, width, height, transformMatrix(transform), true);
		Image dest_img = new Image(dest, image.getName());
		drawImage(dest_img, x_dest, y_dest, anchor);
	}

	@Override
	public void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) {
		int left = x1 < x2 ? x1 : x2;
		left = left < x3 ? left : x3;
		int top = y1 < y2 ? y1 : y2;
		top = top < y3 ? top : y3;
		int right = x1 > x2 ? x1 : x2;
		right = right > x3 ? right : x3;
		int bottom = y1 > y2 ? y1 : y2;
		bottom = bottom > y3 ? bottom : y3;
		if (!checkApplyClip(left, top, right - left, bottom - top)) {
			return;
		}
		
		y1 = -y1;
		y2 = -y2;
		y3 = -y3;
		int i = 0;
		vertices[i++] = (short) x1;
		vertices[i++] = (short) y1;
		vertices[i++] = (short) x2;
		vertices[i++] = (short) y2;
		vertices[i++] = (short) x3;
		vertices[i++] = (short) y3;
		
		GLShortBuffer vertex = GLBufferManager.allocateShortBuffer(i);
		vertex.put(vertices, 0, i);
		AtomicPaint ap = new AtomicPaint(renderColor, i >> 1, vertex, false);
		
		currentPaint.addAtomicPaint(ap);
	}

	@Override
	public void drawRGB(int[] rgbData, int offset, int scanlength, int x,
			int y, int width, int height, boolean processAlpha) {
	}

	@Override
	public void drawBuffer(Canvas canvas, float x, float y, float scale) {
		throw new UnsupportedOperationException("GLGraphics.drawBuffer");
	}

	@Override
	public void setColorFilter(int filterColor) {
		this.filterColor = filterColor;
		renderColor = filterColor(color, filterColor);
	}
	
	private int filterColor(int color1, int color2) {
		if (color1 == 0xFFFFFFFF)
			return color2;
		if (color2 == 0xFFFFFFFF)
			return color1;
		float a1 = ((color1 >> 24) & 0xFF) / 255.0f;
		float r1 = ((color1 >> 16) & 0xFF) / 255.0f;
		float g1 = ((color1 >> 8) & 0xFF) / 255.0f;
		float b1 = (color1 & 0xFF) / 255.0f;
		float a2 = ((color2 >> 24) & 0xFF) / 255.0f;
		float r2 = ((color2 >> 16) & 0xFF) / 255.0f;
		float g2 = ((color2 >> 8) & 0xFF) / 255.0f;
		float b2 = (color2 & 0xFF) / 255.0f;
		int a3 = (int)(a1 * a2 * 255.0f);
		int r3 = (int)(r1 * r2 * 255.0f);
		int g3 = (int)(g1 * g2 * 255.0f);
		int b3 = (int)(b1 * b2 * 255.0f);
		return (a3 << 24) | (r3 << 16) | (g3 << 8) | b3;
	}

	public void drawAll(GL10 gl) {
		GLPaint paint = currentPaint;
		if (!paint.isEmpty()) {
			paint.setScale(scale);
			paint.setTranslate(tx, ty);
			paints.add(paint); // add最后一个paint
		}
		int size = paints.size();
		for (int i = 0; i < size; i++) {
			paints.get(i).draw(gl);
		}
	}
	
	public void clear() {
		for (int i = paints.size() - 1; i >= 0; i--) {
			paints.get(i).clear();
		}
		paints.clear();
		currentPaint = new GLPaint();
		color = filterColor = renderColor = 0xFFFFFFFF;
		clip = null;
	}
}
